﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using StockModel;
using Utils;
using Utils.Extensions;
using Utils.Wpf;
using Visiblox.Charts;

namespace StockAnalyst2.Chart
{
	internal class ChartCommandsVM : ViewModelBase
	{
		#region Attributes

		private readonly ChartViewerVM _chartViewerVM;
		private readonly ChartToolBarVM _chartToolBarVM;

		private readonly ObservableCollection2<StockGroup> _realTimeGroups;
		private DateTime? _loadedDay;
		private string _loadedStock;

		private readonly RawStockDataSource _rawStockDataSource;
		private readonly BackgroundWorker _loadWorker;
		private readonly Generator _generator;
		private readonly QuotationSourceFinder _sourceFinder;
		private readonly DataManager _dataManager;

		#endregion

		#region Properties

		public ICommand RefreshStocksAvailableCommand { get; private set; }
		public ICommand RefreshAvailableDaysCommand { get; private set; }
		public ICommand LoadStockCommand { get; private set; }
		public ICommand ClearStockCommand { get; private set; }
		public ICommand MovePreviousDayCommand { get; private set; }
		public ICommand MoveNextDayCommand { get; private set; }
		public ICommand ShowOptionsCommand { get; private set; }
		public ICommand ZoomCommand { get; private set; }

		#endregion

		#region Events

		public event EventHandler<ErrorDectectedEventArgs> ErrorDetected;
		public event EventHandler<DialogShowRequestedEventArgs> DialogShowRequested;

		#endregion

		#region Public Methods

		public ChartCommandsVM(RawStockDataSource rawStockDataSource, ObservableCollection2<StockGroup> realTimeGroups, QuotationDataSource quotationDataSource, ChartToolBarVM chartToolBarVM, ChartViewerVM chartViewerVM, Generator generator, DataManager dataManager)
		{
			_rawStockDataSource = rawStockDataSource;
			_realTimeGroups = realTimeGroups;
			_chartToolBarVM = chartToolBarVM;
			_chartViewerVM = chartViewerVM;
			_generator = generator;
			_dataManager = dataManager;

			_sourceFinder = new QuotationSourceFinder(rawStockDataSource, realTimeGroups, quotationDataSource);

			_loadWorker = new BackgroundWorker();
			_loadWorker.DoWork += LoadWorker_DoWork; // Local object
			_loadWorker.RunWorkerCompleted += LoadWorker_RunWorkerCompleted; // Local object


			this.RefreshStocksAvailableCommand = new DelegateCommand(this.ExecuteRefreshStocksAvailable);
			this.RefreshAvailableDaysCommand = new DelegateCommand(this.ExecuteRefreshAvailableDays, this.CanRefreshAvailableDays);
			this.ClearStockCommand = new DelegateCommand(this.ExecuteClearStock, this.CanClearStock);
			this.MovePreviousDayCommand = new DelegateCommand(this.ExecuteMovePreviousDay, this.CanMovePreviousDay);
			this.MoveNextDayCommand = new DelegateCommand(this.ExecuteMoveNextDay, this.CanMoveNextDay);
			this.ShowOptionsCommand = new DelegateCommand(this.ExecuteShowOptions);
			this.LoadStockCommand = new DelegateCommand(this.ExecuteLoadStock, this.CanLoadStock);
			this.ZoomCommand = new DelegateCommand(this.ExecuteZoom, this.CanZoom);
		}

		protected virtual void OnErrorDetected(string message)
		{
			if (this.ErrorDetected != null)
			{
				this.ErrorDetected(this, new ErrorDectectedEventArgs(message));
			}
		}

		protected virtual void OnDialogShowRequested(Window dialog)
		{
			if (this.DialogShowRequested != null)
			{
				this.DialogShowRequested(this, new DialogShowRequestedEventArgs(dialog));
			}
		}

		#endregion

		#region Event Handlers

		private void LoadWorker_DoWork(object sender, DoWorkEventArgs e)
		{
			if (_chartToolBarVM.SelectedDate != null || _chartToolBarVM.IsRealTimeEnabled)
			{
				ReadWriteLockedObservableCollection<Quotation> quotationsToChart = _chartToolBarVM.IsRealTimeEnabled ? _sourceFinder.Find(_chartToolBarVM.SelectedStockName) : _sourceFinder.Find(_chartToolBarVM.SelectedStockName, _chartToolBarVM.SelectedDate.Value);

				this.SetChartRange(quotationsToChart);

				_generator.CreateChart(quotationsToChart);

				_dataManager.RefreshDataStatus();

				_loadedDay = _chartToolBarVM.SelectedDate;
				_loadedStock = _chartToolBarVM.SelectedStockName;
			}
		}

		private void LoadWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
		{
			base.IsBusy = false;

			if (e.Error != null)
			{
				this.OnErrorDetected("Unexpected error generating chart.\nError message: " + e.Error.Message);
			}
		}

		#endregion

		#region Private Methods

		private void ExecuteRefreshStocksAvailable()
		{
			try
			{
				_chartToolBarVM.StockNames.Clear();
				foreach (string stockName in this.GetAvailableStockNames())
				{
					_chartToolBarVM.StockNames.Add(stockName);
				}
			}
			catch (Exception ex)
			{
				this.OnErrorDetected("Error refreshing available days. " + ex.Message);
			}
		}

		private void ExecuteRefreshAvailableDays()
		{
			try
			{
				List<DateTime> availableDays = _rawStockDataSource.GetAvailableDays(_chartToolBarVM.SelectedStockName).ToList();

				if (availableDays.Count > 0)
				{
					availableDays.Sort();

					_chartToolBarVM.FirstDate = availableDays[0];
					_chartToolBarVM.LastDate = availableDays[availableDays.Count - 1];
					_chartToolBarVM.SelectedDate = _chartToolBarVM.SelectedDate ?? _chartToolBarVM.LastDate;

					_chartToolBarVM.DaysWithoutData.Clear();
					_chartToolBarVM.DaysWithoutData.AddRange(Date2.GetDatesNotInRange(_chartToolBarVM.FirstDate, _chartToolBarVM.LastDate, availableDays));
				}
			}
			catch (Exception ex)
			{
				this.OnErrorDetected("Error refreshing available days. " + ex.Message);
			}
		}

		private bool CanRefreshAvailableDays()
		{
			return _chartToolBarVM.AreParametersOkToRefreshDays;
		}

		private void ExecuteClearStock()
		{
			_generator.Clear();
			_loadedDay = null;
			_loadedStock = null;
			_dataManager.RefreshDataStatus();
		}

		private bool CanClearStock()
		{
			return _dataManager.IsAnyDataLoaded;
		}

		private void ExecuteMovePreviousDay()
		{
			try
			{
				
				if (_chartToolBarVM.SelectedDate != null)
				{
					DateTime tempDate = _chartToolBarVM.SelectedDate.Value.GetPreviousNonWeekendDay();

					while (_chartToolBarVM.DaysWithoutData.Contains(tempDate))
					{
						tempDate = tempDate.GetPreviousNonWeekendDay();
					}

					_chartToolBarVM.SelectedDate = tempDate;

					if (_dataManager.IsAnyDataLoaded)
					{
						this.ExecuteLoadStock();
					}
				}
			}
			catch (Exception ex)
			{
				this.OnErrorDetected("Error moving previous day. " + ex.Message);
			}
		}

		private bool CanMovePreviousDay()
		{
			return !_chartToolBarVM.IsRealTimeEnabled && _chartToolBarVM.SelectedDate != null && _chartToolBarVM.SelectedDate.Value > _chartToolBarVM.FirstDate;
		}

		private void ExecuteMoveNextDay()
		{
			try
			{
				if (_chartToolBarVM.SelectedDate != null)
				{
					DateTime tempDate = _chartToolBarVM.SelectedDate.Value.GetNextNonWeekendDay();

					while (_chartToolBarVM.DaysWithoutData.Contains(tempDate))
					{
						tempDate = tempDate.GetNextNonWeekendDay();
					}

					_chartToolBarVM.SelectedDate = tempDate;

					if (_dataManager.IsAnyDataLoaded)
					{
						this.ExecuteLoadStock();
					}
				}
			}
			catch (Exception ex)
			{
				this.OnErrorDetected("Error moving next day. " + ex.Message);
			}
		}

		private bool CanMoveNextDay()
		{
			return !_chartToolBarVM.IsRealTimeEnabled && _chartToolBarVM.SelectedDate != null && _chartToolBarVM.SelectedDate.Value < _chartToolBarVM.LastDate;
		}

		private void ExecuteShowOptions()
		{
			try
			{
				OptionsDialog optionsDialog = new OptionsDialog();
				OptionsVM optionsVM = new OptionsVM(_generator.Data.Options);

				optionsDialog.DataContext = optionsVM;

				this.OnDialogShowRequested(optionsDialog);

				if (optionsDialog.DialogResult.HasValue && optionsDialog.DialogResult.Value && _dataManager.IsAnyDataLoaded)
				{
					this.ExecuteLoadStock();
				}
			}
			catch (Exception ex)
			{
				this.OnErrorDetected("Error showing options. " + ex.Message);
			}
		}

		private void ExecuteLoadStock()
		{
			base.IsBusy = true;

			_loadWorker.RunWorkerAsync();
		}

		private bool CanLoadStock()
		{
			return _chartToolBarVM.SelectedStockName != null && (_chartToolBarVM.IsRealTimeEnabled || _chartToolBarVM.SelectedDate != null) && !_loadWorker.IsBusy && (_chartToolBarVM.SelectedDate != _loadedDay || _chartToolBarVM.SelectedStockName != _loadedStock);
		}

		private void ExecuteZoom()
		{
			decimal totalRange = (decimal)(_chartViewerVM.XRange.Maximum - _chartViewerVM.XRange.Minimum).TotalSeconds;
			_chartViewerVM.Zoom.Scale = (double)(_chartViewerVM.ChartAreaWidth / totalRange);
		}

		private bool CanZoom()
		{
			return _dataManager.IsAnyDataLoaded;
		}

		private IEnumerable<string> GetAvailableStockNames()
		{
			List<string> result = new List<string>();

			if (_chartToolBarVM.IsRealTimeEnabled)
			{
				foreach (StockGroup realTimeGroup in _realTimeGroups)
				{
					ObservableCollection2<StockGroupItem> stocks = realTimeGroup.Items.GetReadLock();
					try
					{
						result.AddRange(stocks.Select(s => s.Item.Id.Name));
					}
					finally
					{
						realTimeGroup.Items.FreeReadLock();
					}
				}
				result.Sort();
			}
			else
			{
				result.AddRange(_rawStockDataSource.GetStockNames());
			}

			return result;
		}

		private void SetChartRange(ReadWriteLockedObservableCollection<Quotation> quotationsToChart)
		{
			if (base.Dispatcher.CheckAccess())
			{
				try
				{
					DateTime dateToLoad = quotationsToChart.GetReadLock()[0].Date.Date; // Set the right range based on the date of the quotations. It's almost impossible to have an empty quotation list
					_chartViewerVM.XRange.Minimum = _chartViewerVM.XRange.Minimum.ChangeDate(dateToLoad);
					_chartViewerVM.XRange.Maximum = _chartViewerVM.XRange.Maximum.ChangeDate(dateToLoad);
				}
				finally
				{
					quotationsToChart.FreeReadLock();
				}
			}
			else
			{
				base.Dispatcher.Invoke(new Action<ReadWriteLockedObservableCollection<Quotation>>(this.SetChartRange), new object[] {quotationsToChart});
			}
		}

		#endregion
	}
}
